package name.manana.jarina;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;


/**
 * This represents cache, that will be saved to disk.
 * 
 * @author Marcis
 *
 */
public class CacheJars implements Serializable {
    
    private static final Logger log = Logger.getLogger(CacheJars.class);
    
    public static final File cacheFile = new File("cache-jar.bin");

    /**
     * for serialization
     */
    private static final long serialVersionUID = -1606909499982758538L;
    
    private Set<OneJarEntry> wholeRepository = new TreeSet<OneJarEntry>();
    
    private String mavenRepository;
    
    public CacheJars(String mrepo) {
        setMavenRepository(mrepo);
        makeCacheObject();
        saveToDisk();
    }
    
    public static String makeSrcJarPath(String jarPth) {
        return jarPth.replaceFirst("\\.jar", "-sources.jar");
    }
    
    private void makeCacheObject() {
        FileUtil fileUtil = FileUtil.getInstance();
        log.info("start making list of jars...");
        Set<File> classJars = fileUtil.getAllFiles(mavenRepository, ".jar", new String[] {}, new String[] {"-sources"});
        classJars = reduceJarsByVersion(classJars);
        log.info(classJars.size() + "  *.jar entries found.");
        // zjistit pro kazdej jar, jestli ma sourcy
        for(File classJarFile: classJars) {
            OneJarEntry oneJarEntry = new OneJarEntry();
            oneJarEntry.setJarPath(classJarFile.getPath());
            // maven naming convention
            String src  = makeSrcJarPath(classJarFile.getPath());
            // classes
            JarResources jarUtil = new JarResources(classJarFile.getPath());
            oneJarEntry.setClasses(jarUtil.getFullClassNames());            
            // sources
            if(new File(src).exists()) {
                log.info("src: "+ src);
                oneJarEntry.setSrcExists(Boolean.TRUE);
                JarResources srcJar = new JarResources(src);
                HashMap<String, Set<String>> imports = new HashMap<String, Set<String>>();
                // imports for every java source
                for(String javaName : srcJar.getFullJavaNames()) {
                    imports.put(javaName, srcJar.getAllImports(javaName));
                }
                oneJarEntry.setImports(imports);
            }
            this.wholeRepository.add(oneJarEntry);
        }        
    }
    
    /**
     * remove older jars versions
     * 
     * @param classJars
     * @return
     */
    private Set<File> reduceJarsByVersion(Set<File> classJars) {
        // TODO pokud list obsahuje jinak stejne se jmenujici jary, odstranit starsi verze ze seznamu
        return classJars;
    }

   
    
    /**
     * Saves analysis to filesystem for faster future analysis.
     */
    public void saveToDisk() {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(cacheFile, false));
            oos.writeObject(this);
            oos.close();
        } catch (IOException e) {
            log.error("Cannot save cache file " + cacheFile + ". " + e.getMessage());
        } 
    }


    /**
     * for work with cache file.
     * 
     * @return
     * 		cache file from filesystem
     */
    public static CacheJars loadFromDisk() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(cacheFile));
            Object object = null;
            while ((object = ois.readObject()) != null) {
                if (object instanceof CacheJars) {
                    return (CacheJars) object;
                } else {
                    log.warn("V souboru nema byt nic jineho nez CacheJars objekt!");
                }
            }
        } catch (EOFException e) {
            log.error("End of bin file reached.", e);
            return null;
        } catch (FileNotFoundException e) {
        	log.error("Cache file not found.", e);
            return null;
        } catch (IOException e) {
        	log.error("I/O exc. while loading cache file.", e);
            return null;
        } catch (ClassNotFoundException e) {
        	log.error("Class existing in file not found in app-classpath.", e);
            return null;
		} finally {
            try {
                ois.close();
            } catch (IOException e) {
            	log.error("Can not close cache file!", e);
            }
        }
        return null;
    }
    

    /**
     * @param javaName
     *      full java name
     * @return
     *      jar, which contains entered class
     */
    public OneJarEntry findJar(String javaName) {
        for(OneJarEntry oneJarEntry: wholeRepository) {
            if(oneJarEntry.getClasses().contains(javaName)) {
                return oneJarEntry;
            }
        }
        return null;
    }
    
    
    /**
     * @return the mavenRepository
     */
    public String getMavenRepository() {
        return mavenRepository;
    }

    
    /**
     * @param mavenRepository the mavenRepository to set
     */
    public void setMavenRepository(String mavenRepository) {
        this.mavenRepository = mavenRepository;
    }




    
    /**
     * Represents result of analyzing jar file. 
     * 
     * @author marcis
     */
    class OneJarEntry implements Comparable<OneJarEntry>, Serializable {
        
        /** generated UID    */
        private static final long serialVersionUID = -5816866530358805709L;

        private String jarPath;
        
        private Boolean srcExists = Boolean.FALSE;
        
        private Set<String> classes = new TreeSet<String>();

        private Map<String, Set<String>> imports = new HashMap<String, Set<String>>();
        
 

        
        /**
         * @return the imports
         */
        public Map<String, Set<String>> getImports() {
            if(srcExists.equals(Boolean.FALSE)) {
                return null;
            }
            return imports;
        }


        
        /**
         * @param imports the imports to set
         */
        public void setImports(Map<String, Set<String>> imports) {
            this.imports = imports;
        }


        /**
         * @return the jarPath
         */
        public String getJarPath() {
            return jarPath;
        }

        
        /**
         * @param jarPath the jarPath to set
         */
        public void setJarPath(String jarPath) {
            this.jarPath = jarPath;
        }
        
        
        
        /**
         * @return the jarSourcePath
         */
        public String getJarSourcePath() {
            return jarPath;
        }



        /**
         * @return the srcExists
         */
        public Boolean getSrcExists() {
            return srcExists;
        }


        
        /**
         * @param srcExists the srcExists to set
         */
        public void setSrcExists(Boolean srcExists) {
            this.srcExists = srcExists;
        }


        /**
         * @return the classes
         */
        public Set<String> getClasses() {
            return classes;
        }

        
        /**
         * @param classes the classes to set
         */
        public void setClasses(Set<String> classes) {
            this.classes = classes;
        }
        
        
        @Override
        public int hashCode() {
            return this.jarPath.hashCode();
        }
        
        @Override
        public boolean equals(Object obj) {
            return this.jarPath.equals(obj);
        }
        
        @Override
        public String toString() {
            return this.jarPath;
        }


        public int compareTo(OneJarEntry obj) {
            if (obj instanceof OneJarEntry) {
                OneJarEntry other = (OneJarEntry) obj;
                return this.jarPath.compareTo(other.jarPath);
            } else {
                throw new ClassCastException("Cannot cast "+ obj.getClass() + " to OneJarEntry! ");
            }
        }
        
    }


 

}
